package com.msi.algorithm;

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;

import com.msi.Graph;
import com.msi.Main;
import com.msi.Vertex;

/**
 * Stan częściowy algorytmu CSP
 * 
 * @author Michał Kołodziejski
 */
public class State {

    /**
     * Liczba istniejących stanów
     */
    private static int stateCount = 0;

    /**
     * Identyfikator stanu
     */
    protected int stateId;

    /**
     * Kopia grafu połączeń
     */
    protected Graph graph;

    /**
     * Aktualnie rozpatrywany wierzchołek grafu
     */
    protected final Vertex currentVertex;

    /**
     * Lista stołów z gośćmi
     */
    protected final List<Table> tables;

    /**
     * Kolejne stany drzewa
     */
    protected List<State> childrenStates;

    /**
     * Lista kolejnych gości do obsadzenia
     */
    protected List<Vertex> possibleChildrenStatesVertices;

    /**
     * Informacja, czy w tym stanie gość już został obsadzony
     */
    protected boolean guestPlaced;

    /**
     * Informacja czy stan jest finalny
     */
    protected boolean isStateFinal;

    /**
     * Poprzedni stan
     */
    protected State parentState;

    /**
     * Logger
     */
    protected Logger logger = Logger.getLogger(State.class);

    /**
     * Tworzy nowy stan w drzewie
     * 
     * @param graph
     *            graf połączeń
     * @param currentVertex
     *            aktualnie rozpatrywany wierzchołek grafu
     * @param tables
     *            lista stołów z gośćmi
     * @param parentState
     *            poprzedni stan
     */
    public State(Graph graph, Vertex currentVertex, List<Table> tables,
            State parentState) {
        this.stateId = stateCount++;
        this.graph = graph;
        this.currentVertex = currentVertex;
        this.parentState = parentState;
        this.tables = tables;
        this.childrenStates = new ArrayList<State>();
        this.guestPlaced = false;

        // currentVertex musi należeć do tej kopii grafu!!!
        this.currentVertex.markAsVisited();
        this.possibleChildrenStatesVertices = this.graph
                .getNotVisitedVerticesSortedByDegree(Main.ascendingSort);
        this.isStateFinal = checkIfStateIsFinal();
    }

    /**
     * Sadza gościa przy stole. W razie potrzeby dodaje nowy stół
     * 
     * @return informacja, czy został utworzony nowy stół
     */
    public boolean placeCurrentGuest() {
        if (guestPlaced) {
            return false;
        }

        boolean guestPlacedAtExistingTable = false;

        for (Table table : tables) {
            if (table.maySeatHere(currentVertex)) {
                table.sitHere(currentVertex);
                guestPlacedAtExistingTable = true;

                if (table.getNumberOfSittingGuests() == 4) {
                    // wyłączamy te wierzchołki z grafu
                    for (Vertex guest : table.getListOfSittingGuests()) {
                        guest.exclude();
                    }
                }

                break;
            }
        }

        if (!guestPlacedAtExistingTable) {
            // nie było gdzie go posadzić - dostawiamy stół
            Table newTable = new Table();
            newTable.sitHere(currentVertex);
            tables.add(newTable);
        }

        guestPlaced = true;

        return !guestPlacedAtExistingTable;
    }

    /**
     * Zwraca informację, czy węzeł może mieć więcej dzieci niż do tej pory
     * 
     * @return informacja, czy węzeł może mieć więcej dzieci niż do tej pory
     */
    public boolean mayHaveMoreChildrenStates() {
        // logger.debug("possibleChildrenStatesVertices size: "+possibleChildrenStatesVertices.size());
        return (possibleChildrenStatesVertices.size() != 0);
    }

    /**
     * Tworzy nowy stan
     * 
     * @return nowoutworzony stan
     */
    public State createNewChildState() {
        if (!mayHaveMoreChildrenStates()) {
            throw new RuntimeException("This node (vertex: "
                    + currentVertex.getId() + ") cannot have more children!");
        }

        Vertex nextVertex = possibleChildrenStatesVertices.get(0);
        possibleChildrenStatesVertices.remove(0);

        // kopia grafu
        Graph graphCopy = graph.getDeepCopy();
        // przekazywany wierzchołek musi należeć do kopii grafu
        nextVertex = graphCopy.getVertex(nextVertex.getId());

        // kopia stołów
        List<Table> tablesCopy = new ArrayList<Table>();
        for (Table table : tables) {
            tablesCopy.add(table.getTableCopy(graphCopy));
        }

        // nowy stan
        State newChildState = new State(graphCopy, nextVertex, tablesCopy, this);
        this.childrenStates.add(newChildState);

        return newChildState;
    }

    /**
     * Zwraca informację, czy stan jest finalny, a więc czy wszyscy goście
     * zostali już obsadzeni
     * 
     * @return true jeżeli stan jest finalny, false w przeciwnym przypadku
     */
    public boolean isStateFinal() {
        return isStateFinal;
    }

    /**
     * Zwraca listę stołów z obsadzonymi gośćmi w tym stanie
     * 
     * @return lista stołów
     */
    public List<Table> getTables() {
        return tables;
    }

    /**
     * Zwraca ilość stołów
     * 
     * @return ilość stołów
     */
    public int getTablesNumber() {
        return tables.size();
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("[");
        sb.append("stateId: " + stateId);
        sb.append(", parentStateId: "
                + ((parentState != null) ? parentState.stateId : "null"));
        sb.append(", vertex: " + currentVertex.getId());
        sb.append(", isStateFinal: " + isStateFinal);
        sb.append(", tables: " + tables.size());
        sb.append("]");
        return sb.toString();
    }

    /**
     * Sprawdza, czy stan jest finalny
     * 
     * @return true jeżeli stan jest finalny, false w przeciwnym przypadku
     */
    protected boolean checkIfStateIsFinal() {
        for (Vertex v : graph.getVertices().values()) {
            if (!v.isVisited()) {
                // logger.debug("[checkIfStateIsFinal] NOT final, not visited vertex: "
                // + v.getId());
                return false;
            }
        }

        // logger.debug("[checkIfStateIsFinal] FINAL");
        return true;
    }

    /**
     * Zwraca liczbę posadzonych już gości
     * 
     * @return liczba posadzonych gości
     */
    protected int getNumberOfSittingGuests() {
        int number = 0;
        for (Table t : tables) {
            number += t.getNumberOfSittingGuests();
        }
        return number;
    }
}
